﻿
using System;
using System.Collections.Generic;
using System.Data;
using System.IO;

namespace CatEars.Core.FileFormat.Csv
{
    /// <summary>
    /// Csv文件写入类
    /// </summary>
    public static class CsvWriter
    {
        #region WriteDataTable
        /// <summary>
        /// 保存数据到流
        /// </summary>
        /// <param name="csvDefine">csv格式定义 null则按默认格式处理</param>
        /// <param name="sw">流对象</param>
        /// <param name="dtSource">源表格</param>
        /// <param name="listHeader">标题集合 为null时使用dtSource的列名作为标题</param>
        /// <param name="listOrder">写入列及顺序 为null时使用dtSource的列顺序</param>
        /// <param name="callback">回调函数,每写入一行回调一次 可用于进度提示</param>
        /// <returns>写入字节数</returns>
        /// <exception cref="ArgumentException">标题行列数与需要导出的列数不一致</exception>
        public static long WriteDataTable(
            CsvDefine csvDefine,
            StreamWriter sw,
            DataTable dtSource,
            IList<string> listHeader,
            IList<string> listOrder,
            Action<int> callback = null)
        {
            if (csvDefine == null)
            {
                csvDefine = CsvDefine.Default;
            }

            long lngStart = 0;
            if (sw.BaseStream.CanSeek)
            {
                lngStart = sw.BaseStream.Position;
            }

            if (listOrder != null && listOrder.Count == 0)
            {
                listOrder = null;
            }
            int intColCount = listOrder != null ? listOrder.Count : dtSource.Columns.Count;
            if (intColCount == 0 || dtSource.Columns.Count == 0)
            {
                //没有任何列可以导出
                return 0;
            }
            WriteCsvHeader(csvDefine, sw, dtSource, listHeader, listOrder, intColCount);

            string[] fieldArr = new string[intColCount];
            int intRowIndex = 0;

            if (listOrder != null)
            {
                foreach (DataRow row in dtSource.Rows)
                {
                    for (int i = 0; i < intColCount; i++)
                    {
                        fieldArr[i] = row[listOrder[i]].ToString();
                    }
                    CsvCore.WriteOneCsvRow(csvDefine, sw, fieldArr, false);
                    if (callback != null)
                    {
                        intRowIndex++;
                        callback(intRowIndex);
                    }
                }
            }
            else
            {
                foreach (DataRow row in dtSource.Rows)
                {
                    for (int i = 0; i < intColCount; i++)
                    {
                        fieldArr[i] = row[i].ToString();
                    }
                    intRowIndex++;
                    bool blnFlush = (csvDefine.AutoFlushRowCount > 0) && (intRowIndex % csvDefine.AutoFlushRowCount == 0);
                    CsvCore.WriteOneCsvRow(csvDefine, sw, fieldArr, blnFlush);
                    if (callback != null)
                    {
                        callback(intRowIndex);
                    }
                }
            }
            if (!sw.AutoFlush && csvDefine.AutoFlushRowCount > 0)
            {
                sw.Flush();
            }
            if (sw.BaseStream.CanSeek)
            {
                return sw.BaseStream.Position - lngStart;
            }
            else
            {
                return 0;
            }
        }

        /// <summary>
        /// 写入Csv标题行
        /// </summary>
        /// <param name="csvDefine">csv格式定义 不能为null</param>
        /// <param name="sw">StreamWriter流对象</param>
        /// <param name="dtSource">源表格</param>
        /// <param name="listHeader">标题集合 为null时使用dtSource的列名作为标题</param>
        /// <param name="listOrder">写入列及顺序 为null时使用dtSource的列顺序</param>
        /// <param name="intColCount">csv文件总列数</param>
        private static void WriteCsvHeader(
            CsvDefine csvDefine,
            StreamWriter sw,
            DataTable dtSource,
            IList<string> listHeader,
            IList<string> listOrder,
            int intColCount)
        {
            if (!csvDefine.FristRowIsHeader)
            {
                return;
            }
            if (listHeader != null && listHeader.Count == 0)
            {
                listHeader = null;
            }
            if (listHeader == null)
            {
                listHeader = new List<string>(intColCount);
                var cols = dtSource.Columns;
                if (listOrder != null)
                {
                    for (int i = 0; i < intColCount; i++)
                    {
                        listHeader.Add(listOrder[i]);
                        //listHeader.Add(cols[listOrder[i]].ColumnName);
                    }
                }
                else
                {
                    for (int i = 0; i < intColCount; i++)
                    {
                        listHeader.Add(cols[i].ColumnName);
                    }
                }
            }
            else if (listHeader.Count != intColCount)
            {
                throw new ArgumentException(
                    "标题行列数与需要导出的列数不一致");
            }
            CsvCore.WriteOneCsvRow(csvDefine, sw, listHeader, false);
        }
        #endregion

        #region WriteDataTable重载
        /// <summary>
        /// 保存DataTable到csv文件
        /// </summary>
        /// <param name="csvDefine">csv格式定义 null则按默认格式处理</param>
        /// <param name="strFilePath">文件路径</param>
        /// <param name="dtSource">要写入的数据表</param>
        /// <param name="dictHeaderMapping">标题行映射关系及顺序</param>
        /// <param name="callback">回调函数,每写入一行回调一次 可用于进度提示</param>
        public static void WriteDataTable(CsvDefine csvDefine,
            string strFilePath,
            DataTable dtSource,
            Dictionary<string, string> dictHeaderMapping = null,
            Action<int> callback = null)
        {
            WriteDataTable(csvDefine, strFilePath, FileMode.Create, dtSource, dictHeaderMapping, callback);
        }

        /// <summary>
        /// 保存DataTable到csv文件
        /// </summary>
        /// <param name="csvDefine">csv格式定义 null则按默认格式处理</param>
        /// <param name="strFilePath">文件路径</param>
        /// <param name="fileMode">文件模式 建议Create或者CreateNew</param>
        /// <param name="dtSource">要写入的数据表</param>
        /// <param name="dictHeaderMapping">标题行映射关系及顺序</param>
        /// <param name="callback">回调函数,每写入一行回调一次 可用于进度提示</param>
        public static void WriteDataTable(CsvDefine csvDefine,
            string strFilePath,
            FileMode fileMode,
            DataTable dtSource,
            Dictionary<string, string> dictHeaderMapping = null,
            Action<int> callback = null)
        {
            using (FileStream fs = new FileStream(strFilePath, fileMode, FileAccess.Write))
            {
                WriteDataTable(csvDefine, fs, dtSource, dictHeaderMapping, callback);
            }
        }

        /// <summary>
        /// 保存DataTable到Stream
        /// </summary>
        /// <param name="csvDefine">csv格式定义 null则按默认格式处理</param>
        /// <param name="stream">Stream流对象</param>
        /// <param name="dtSource">要写入的数据表</param>
        /// <param name="dictHeaderMapping">标题行映射关系及顺序</param>
        /// <param name="callback">回调函数,每写入一行回调一次 可用于进度提示</param>
        public static void WriteDataTable(CsvDefine csvDefine,
            Stream stream,
            DataTable dtSource,
            Dictionary<string, string> dictHeaderMapping = null,
            Action<int> callback = null)
        {
            if (csvDefine == null)
            {
                csvDefine = CsvDefine.Default;
            }
            //sw不需要Dispose，由外部对stream进行Dispose
            StreamWriter sw = new StreamWriter(stream, csvDefine.GetEncoding());
            WriteDataTable(csvDefine, sw, dtSource, dictHeaderMapping, callback);
        }

        /// <summary>
        /// 保存DataTable到StreamWriter
        /// </summary>
        /// <param name="csvDefine">csv格式定义 null则按默认格式处理</param>
        /// <param name="sw">StreamWriter流对象</param>
        /// <param name="dtSource">要写入的数据表</param>
        /// <param name="dictHeaderMapping">标题行映射关系及顺序</param>
        /// <param name="callback">回调函数,每写入一行回调一次 可用于进度提示</param>
        public static void WriteDataTable(
            CsvDefine csvDefine,
            StreamWriter sw,
            DataTable dtSource,
            Dictionary<string, string> dictHeaderMapping = null,
            Action<int> callback = null)
        {
            List<string> listField = null;
            List<string> listOrder = null;
            if (dictHeaderMapping != null && dictHeaderMapping.Count > 0)
            {
                listField = new List<string>(dictHeaderMapping.Count);
                listOrder = new List<string>(dictHeaderMapping.Count);
                foreach (var kv in dictHeaderMapping)
                {
                    listOrder.Add(kv.Key);
                    var value = kv.Value;
                    if (value != null)
                    {
                        listField.Add(kv.Value);
                    }
                    else
                    {
                        listField.Add(kv.Key);
                    }
                }
            }
            WriteDataTable(csvDefine, sw, dtSource, listField, listOrder, callback);
        }
        #endregion

        #region WriteEntity<T>

        /// <summary>
        /// 保存对象集合到csv文件
        /// </summary>
        /// <param name="csvDefine">csv格式定义 null则按默认格式处理</param>
        /// <param name="strFilePath">文件路径</param>
        /// <param name="funcGetRow">Entoty转csv行</param>
        /// <param name="source">要输出的对象</param>
        /// <param name="header">标题行</param>
        /// <param name="callback">回调函数,每写入一行回调一次 可用于进度提示</param>
        public static void WriteEntity<T>(
            CsvDefine csvDefine,
            string strFilePath,
            Func<T, ICollection<string>> funcGetRow,
            IEnumerable<T> source,
            ICollection<string> header = null,
            Action<int> callback = null)
        {
            WriteEntity<T>(csvDefine, strFilePath, FileMode.Create, funcGetRow, source, header, callback);
        }

        /// <summary>
        /// 保存对象集合到csv文件
        /// </summary>
        /// <param name="csvDefine">csv格式定义 null则按默认格式处理</param>
        /// <param name="strFilePath">文件路径</param>
        /// <param name="fileMode">文件模式 建议Create或者CreateNew</param>
        /// <param name="funcGetRow">Entoty转csv行</param>
        /// <param name="source">要输出的对象</param>
        /// <param name="header">标题行</param>
        /// <param name="callback">回调函数,每写入一行回调一次 可用于进度提示</param>
        public static void WriteEntity<T>(
            CsvDefine csvDefine,
            string strFilePath,
            FileMode fileMode,
            Func<T, ICollection<string>> funcGetRow,
            IEnumerable<T> source,
            ICollection<string> header = null,
            Action<int> callback = null)
        {
            using (FileStream fs = new FileStream(strFilePath, fileMode, FileAccess.Write))
            {
                WriteEntity<T>(csvDefine, fs, funcGetRow, source, header, callback);
            }
        }

        /// <summary>
        /// 保存对象集合到流
        /// </summary>
        /// <param name="csvDefine">csv格式定义 null则按默认格式处理</param>
        /// <param name="stream">Stream流对象</param>
        /// <param name="funcGetRow">Entoty转csv行</param>
        /// <param name="source">要输出的对象</param>
        /// <param name="header">标题行</param>
        /// <param name="callback">回调函数,每写入一行回调一次 可用于进度提示</param>
        public static void WriteEntity<T>(
            CsvDefine csvDefine,
            Stream stream,
            Func<T, ICollection<string>> funcGetRow,
            IEnumerable<T> source,
            ICollection<string> header = null,
            Action<int> callback = null)
        {
            if (csvDefine == null)
            {
                csvDefine = CsvDefine.Default;
            }
            //sw不需要Dispose，由外部对stream进行Dispose
            StreamWriter sw = new StreamWriter(stream, csvDefine.GetEncoding());
            WriteEntity<T>(csvDefine, sw, funcGetRow, source, header, callback);
        }

        /// <summary>
        /// 保存对象集合到流
        /// </summary>
        /// <param name="csvDefine">csv格式定义 null则按默认格式处理</param>
        /// <param name="sw">StreamWriter流对象</param>
        /// <param name="funcGetRow">Entoty转csv行</param>
        /// <param name="source">要输出的对象</param>
        /// <param name="header">标题行</param>
        /// <param name="callback">回调函数,每写入一行回调一次 可用于进度提示</param>
        public static void WriteEntity<T>(
            CsvDefine csvDefine,
            StreamWriter sw,
            Func<T, ICollection<string>> funcGetRow,
            IEnumerable<T> source,
            ICollection<string> header = null,
            Action<int> callback = null)
        {
            if (header != null && header.Count > 0)
            {
                CsvCore.WriteOneCsvRow(csvDefine, sw, header, false);
            }
            int intRowIndex = 0;
            foreach (var entity in source)
            {
                var strRow = funcGetRow(entity);
                intRowIndex++;
                bool blnFlush = (csvDefine.AutoFlushRowCount > 0) && (intRowIndex % csvDefine.AutoFlushRowCount == 0);
                CsvCore.WriteOneCsvRow(csvDefine, sw, strRow, blnFlush);
                if (callback != null)
                {
                    callback(intRowIndex);
                }
            }
            if (!sw.AutoFlush && csvDefine.AutoFlushRowCount > 0)
            {
                sw.Flush();
            }
        }

        #endregion

        #region 其它

        /// <summary>
        /// 打开一个Csv文件
        /// </summary>
        /// <param name="csvDefine">csv格式定义 null则按默认格式处理</param>
        /// <param name="strFilePath">文件路径</param>
        /// <returns>文件流</returns>
        public static StreamWriter OpenFile(CsvDefine csvDefine,
            string strFilePath)
        {
            if (csvDefine == null)
            {
                csvDefine = CsvDefine.Default;
            }
            FileStream fs = new FileStream(strFilePath, FileMode.Create, FileAccess.Write);
            StreamWriter sw = new StreamWriter(fs, csvDefine.GetEncoding());
            return sw;
        }

        #endregion
    }
}
